using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.SystemUI;
using ESRI.ArcGIS.Framework;
using ESRI.ArcGIS.ADF.CATIDs;
using BesAsm.Emgaats.Core;
using BesAsm.Emgaats.ModelBuilder;
using BesAsm.MasterData.Services;
using BesAsm.MasterData.Entities;
using BesAsm.Framework.Reporting;
using BesAsm.Framework.Collections;
using System.Xml;
using BesAsm.Emgaats.Core.Commands;
using BesAsm.Emgaats.ModelBuilder.Commands;
using System.Reflection;

namespace BesAsm.Emgaats.ModelBuilderUI
{
  /// <summary>
  /// User interface for building models
  /// </summary>  
  [ClassInterface(ClassInterfaceType.None)]
  public partial class ModelBuilderUserControl : UserControl
  {
    private const int CALIBRATION_STORM_TYPE_IMAGE_INDEX = 0;
    private const int DESIGN_STORM_TYPE_IMAGE_INDEX = 1;

    private ModelBuilderExtension _modelBuilderExtension;

    /// <summary>
    /// Creates a model builder user control
    /// </summary>
    public ModelBuilderUserControl(object hook)
    {
      InitializeComponent();

      try
      {
        Hook = hook;

        _modelBuilderExtension = ModelBuilderExtension.GetExtension();
        _modelBuilderExtension.RebindRequired += new EventHandler(OnRebindRequired);

        Mediator.ModelLoaded += new EventHandler(Mediator_ModelLoaded);
        LayerLibrarian.CheckedIn += ModelBuilderExtension_TraceLinksChanged;
      }
      catch (Exception ex)
      {
        Messenger.ReportMessage("Unable to load ModelBuilder interface: " + ex.Message, ReportableMessageType.Error);
      }

    }

    void Mediator_ModelLoaded(object sender, EventArgs e)
    {
      if (Mediator.Model.State > ModelState.Null)
      {
        BindUI(Mediator.Model);
      }
      else
        ClearUIBindings();
    }

    private void ModelBuilderExtension_TraceLinksChanged(object sender, CheckInCheckOutArgs e)
    {
      //TODO: Perform any necessary actions when the trace data changes
      //NOTE: Functionality that was present here has been assigned to ModelBuilderUserContorl
      if (e.LibraryItem != LibraryItem.TraceLinks)
        return;

      
      EmgaatsList<Link> traceLinks = new EmgaatsList<Link>(ArcMap.Application, e.Layer);

      if (traceLinks != null && traceLinks.Count > 1)
        this.btnBuild.Text = string.Format("Build ({0} links)",
          traceLinks.Count);
      else if (traceLinks != null && traceLinks.Count == 1)
        this.btnBuild.Text = "Build (1 link)";
      else
        this.btnBuild.Text = "Build";
    }

    /// <summary>
    /// Host object of the dockable window
    /// </summary>
    private object Hook
    {
      get;
      set;
    }

    /// <summary>
    /// Implementation class of the dockable window add-in. It is responsible for 
    /// creating and disposing the user interface class of the dockable window.
    /// </summary>
    public class AddinImpl : ESRI.ArcGIS.Desktop.AddIns.DockableWindow
    {
      private ModelBuilderUserControl m_windowUI;

      public AddinImpl()
      {
      }

      protected override IntPtr OnCreateChild()
      {
        m_windowUI = new ModelBuilderUserControl(this.Hook);
        return m_windowUI.Handle;
      }

      protected override void Dispose(bool disposing)
      {
        if (m_windowUI != null)
          m_windowUI.Dispose(disposing);

        base.Dispose(disposing);
      }

    }

    private void OnRebindRequired(object sender, EventArgs e)
    {
      //TODO: Rebind model builder layers as needed

    }

    /// <summary>
    /// Removes all data bindings from this user control's embedded controls
    /// </summary>
    public void ClearUIBindings()
    {
      var allControls = from nested in
                          this.Controls.Cast<Control>().
                          Descendants(c => c.Controls.Cast<Control>())
                        select nested;
      var clearBindingControls = from c in allControls
                                 where c.DataBindings.Count > 0
                                 select c;
      foreach (Control control in clearBindingControls)
        control.DataBindings.Clear();
    }

    /// <summary>
    /// Retrieves and formats the name of the provided storm
    /// </summary>
    /// <param name="storm">Storm of interest</param>
    /// <returns>Formatted string for use in displaying the storm name</returns>
    private static string GetStormName(Storm storm)
    {
      return string.Format("[{0}] {1}", storm.Abbreviation, storm.Name);
    }

    /// <summary>
    /// Populates the simulation list on the first page of the model builder using the model's
    /// current list of simulations
    /// </summary>
    /// <param name="model">The model of interest</param>
    private void PopulateSimulationList(Model model)
    {
      lstViewSimulations.Items.Clear();
      foreach (Simulation simulation in model.Simulations)
      {
        Infragistics.Win.UltraWinListView.UltraListViewItem simItem =
          new Infragistics.Win.UltraWinListView.UltraListViewItem(simulation.Id.ToString());

        Storm storm = GetStormById(simulation.Id);
        if (simulation.Name == null || simulation.Name.Length == 0)
          simulation.Name = GetStormName(storm);
        else if (storm == null)
          simulation.Name = "Unknown storm";

        simItem.Value = simulation.Name;

        switch (storm.Type)
        {
          case "C":
            simItem.Appearance.Image = CALIBRATION_STORM_TYPE_IMAGE_INDEX;
            break;
          case "D":
            simItem.Appearance.Image = DESIGN_STORM_TYPE_IMAGE_INDEX;
            break;
        }
        lstViewSimulations.Items.Add(simItem);
      }
    }

    /// <summary>
    /// Binds the user interface for the model builder to model data
    /// </summary>
    /// <param name="model">The model of interest</param>
    public void BindUI(Model model)
    {
      System.Diagnostics.Debug.WriteLine("Rebinding UI");
      try
      {
        ClearUIBindings();
        txtModelName.DataBindings.Add(new Binding("Text", model, "Name"));
        txtModelDescription.DataBindings.Add(new Binding("Text", model, "Description"));
        cmbTimeFrame.DataBindings.Add(new Binding("Text", model, "TimeFrame"));
        lstViewSimulations.Items.Clear();
        PopulateSimulationList(model);
        UpdateLookups();

        lstViewBuildInstructions.Items.Clear();
        Infragistics.Win.UltraWinListView.UltraListViewItem buildAllItem =
          lstViewBuildInstructions.Items.Add("Build model", "Build model");
        buildAllItem.CheckState = CheckState.Checked;
        lstViewBuildInstructions.Items.Add("Prepare model directory", "Prepare model directory");
        lstViewBuildInstructions.Items.Add("Build model geodatabase", "Build model geodatabase");
        lstViewBuildInstructions.Items.Add("Deploy stormwater controls", "Deploy stormwater controls");
        lstViewBuildInstructions.Items.Add("Deploy hydrology", "Deploy hydrology");
        lstViewBuildInstructions.Items.Add("Deploy hydraulics", "Deploy hydraulics");
      }
      catch (Exception ex)
      {
        throw ex;
      }
    }

    private void btnModifySimulations_Click(object sender, EventArgs e)
    {
      // TODO: Prepare simulation info here
      tabModelBuilder.SelectedTab = tabModelBuilder.Tabs["Modify Simulations"];
    }

    private void btnReturnToBuildPage_Click(object sender, EventArgs e)
    {
      // TODO: Apply simulation changes here
      tabModelBuilder.SelectedTab = tabModelBuilder.Tabs["Build Specs"];
    }

    private void btnBuild_Click(object sender, EventArgs e)
    {
      Model model = null;
      try
      {
        //_modelBuilderExtension.BuildModel();

        // This is really half-assed and needs to be refactored so it can dynamically change
        // as we add commands

        DateTime startBuildTime = DateTime.Now;
        Messenger.ReportMessage("Building model", BesAsm.Framework.Reporting.ReportableMessageType.Info);

        model = _modelBuilderExtension.Model;
        model.State = ModelState.Assembled;

        var selectedBuildInstructions = from i in lstViewBuildInstructions.Items.Cast<Infragistics.Win.UltraWinListView.UltraListViewItem>()
                                        where i.CheckState == CheckState.Checked
                                        select i;
        MultiCommand buildCommands = new MultiCommand();
        foreach (var item in selectedBuildInstructions)
        {
          switch (item.Key)
          {
            case "Build model":
              buildCommands.AddCommand(new PrepareModelDirectoryCommand(model));
              buildCommands.AddCommand(new BuildPgdbCommand(model, ArcMap.Application));
              buildCommands.AddCommand(new DeployStormwaterControlsCommand(model));
              buildCommands.AddCommand(new DeployHydrologyCommand(model));
              buildCommands.AddCommand(new DeployHydraulicsCommand(model));
              buildCommands.Execute();
              _modelBuilderExtension.SetModelDataSource();
              _modelBuilderExtension.LoadModelLayersToMap();
              break;
            case "Prepare model directory":
              buildCommands.AddCommand(new PrepareModelDirectoryCommand(model));
              buildCommands.Execute();
              break;
            case "Build model geodatabase":
              buildCommands.AddCommand(new BuildPgdbCommand(model, ArcMap.Application));
              buildCommands.Execute();
              _modelBuilderExtension.SetModelDataSource();
              _modelBuilderExtension.LoadModelLayersToMap();
              break;
            case "Deploy stormwater controls":
              buildCommands.AddCommand(new DeployStormwaterControlsCommand(model));
              buildCommands.Execute();
              break;
            case "Deploy hydrology":
              buildCommands.AddCommand(new DeployHydrologyCommand(model));
              buildCommands.Execute();
              break;
            case "Deploy hydraulics":
              buildCommands.AddCommand(new DeployHydraulicsCommand(model));
              buildCommands.Execute();
              break;
          }
        }

        DateTime endBuildTime = DateTime.Now;
        TimeSpan buildDuration = endBuildTime.Subtract(startBuildTime);
        Messenger.ReportMessage(
          string.Format("Build ended ({0}:{1:00}:{2:00}.{3:000})", buildDuration.Hours,
            buildDuration.Minutes, buildDuration.Seconds, buildDuration.Milliseconds),
          BesAsm.Framework.Reporting.ReportableMessageType.Info);

        //MessageBox.Show(
        //  string.Format("{0} dscs, {1} links, {2} nodes, {3} parking targets, {4} roof targets, " +
        //  "{5} sscs, {6} street targets found",
        //  model.Dscs.Count, model.Links.Count, model.Nodes.Count,
        //  model.ParkingTargets.Count, model.RoofTargets.Count, model.Sscs.Count,
        //  model.StreetTargets.Count));
      }
      catch (Exception ex)
      {
        MessageBox.Show("Unable to build model: " + ex.Message, "Error Building Model", MessageBoxButtons.OK, MessageBoxIcon.Error);
        Messenger.ReportMessage("Error building model: " + ex.Message, ReportableMessageType.Error);
        if (model != null)
          model.State = ModelState.HasErrors;
      }
    }

    private void ModelBuilderUserControl_Load(object sender, EventArgs e)
    {
      UpdateLookups();
    }

    /// <summary>
    /// Updates the lookup combo box using server data
    /// </summary>
    private void UpdateTimeFrameLookup()
    {
      TimeFrameService timeFrameService = new TimeFrameService();
      TList<TimeFrame> availableTimeFrames = timeFrameService.GetAll();
      cmbTimeFrame.Items.Clear();
      foreach (TimeFrame timeFrame in availableTimeFrames)
        cmbTimeFrame.Items.Add(timeFrame.TimeFrameId, timeFrame.Name);
    }

    /// <summary>
    /// Indicates whether the current model has a simulation with the supplied storm ID
    /// </summary>
    /// <param name="stormId">ID of storm</param>
    /// <returns>True if simulation with storm is available</returns>
    private bool ModelHasStorm(int stormId)
    {
      return _modelBuilderExtension.Model.Simulations.Count(s => s.Id == stormId) > 0;
    }

    /// <summary>
    /// Returns the simulation corresponding to the supplied storm ID
    /// </summary>
    /// <param name="stormId">ID of storm</param>
    /// <returns>The Simulation containing the requested storm, or null if the model does not have the storm</returns>
    private Simulation ModelSimulation(int stormId)
    {
      return _modelBuilderExtension.Model.Simulations.Where(s => s.Id == stormId).First();
    }

    /// <summary>
    /// Updates the storm combo box with values from the server
    /// </summary>
    private void UpdateStormLookup()
    {
      StormService stormService = new StormService();
      TList<Storm> availableStorms = stormService.GetAll();

      lstViewStorms.Items.Clear();
      foreach (Storm storm in availableStorms)
      {
        Infragistics.Win.UltraWinListView.UltraListViewItem stormItem =
          lstViewStorms.Items.Add(storm.StormId.ToString(), storm.Name);
        switch (storm.Type)
        {
          case "C":
            stormItem.Appearance.Image = CALIBRATION_STORM_TYPE_IMAGE_INDEX;
            break;
          case "D":
            stormItem.Appearance.Image = DESIGN_STORM_TYPE_IMAGE_INDEX;
            break;
        }
        if (_modelBuilderExtension.Model.Simulations != null)
        {
          stormItem.CheckState = (ModelHasStorm(storm.StormId)) ?
            CheckState.Checked : CheckState.Unchecked;
        }
      }
    }

    /// <summary>
    /// Update all server-based combo boxes with values from the server
    /// </summary>
    private void UpdateLookups()
    {
      try
      {
        UpdateTimeFrameLookup();
        UpdateStormLookup();
      }
      catch (Exception ex)
      {
        Messenger.ReportMessage("Unable to update lookups: " + ex.Message, ReportableMessageType.Error);
      }
    }

    private void btnCancelBuild_Click(object sender, EventArgs e)
    {
      // TODO: Prepare building cancel commands here
      tabModelBuilder.SelectedTab = tabModelBuilder.Tabs["Build Specs"];
    }

    private void cmbTimeFrame_ItemNotInList(object sender, Infragistics.Win.UltraWinEditors.ValidationErrorEventArgs e)
    {
      if (cmbTimeFrame.Value == null && cmbTimeFrame.Items.Count > 0)
        cmbTimeFrame.Value = cmbTimeFrame.Items[0];
    }

    /// <summary>
    /// Retrieves a storm record from the server given a Storm ID
    /// </summary>
    /// <param name="selectedStormId">The ID of the storm of interest</param>
    /// <returns>A Storm object</returns>
    private static Storm GetStormById(int selectedStormId)
    {
      StormService stormService = new StormService();
      Storm selectedStorm = stormService.GetByStormId(selectedStormId);
      return selectedStorm;
    }

    /// <summary>
    /// Updates the list of simulations with the current model's simulations
    /// </summary>
    private void UpdateSimulationList()
    {
      PopulateSimulationList(_modelBuilderExtension.Model);
    }

    /// <summary>
    /// Gets all the controls in the user control
    /// </summary>
    /// <returns>A list of all controls</returns>
    private IEnumerable<Control> GetAllControls()
    {
      var allControls = from nested in
                          this.Controls.Cast<Control>().
                          Descendants(c => c.Controls.Cast<Control>())
                        select nested;
      return allControls;
    }

    /// <summary>
    /// Gets all the controls with the Simulation tag and have been bound to a data object
    /// </summary>
    /// <returns>A list of all bound simulation-related controls</returns>
    private IEnumerable<Control> GetBoundSimulationControls()
    {
      IEnumerable<Control> allControls = GetAllControls();
      var clearBindingControls = from c in allControls
                                 where c.DataBindings.Count > 0 && c.Tag == "Simulation"
                                 select c;
      return clearBindingControls;
    }

    /// <summary>
    /// Gets all the controls with the Simulation tag
    /// </summary>
    /// <returns>A list of all simulation-related controls</returns>
    private IEnumerable<Control> GetSimulationControls()
    {
      IEnumerable<Control> allControls = GetAllControls();
      var clearBindingControls = from c in allControls
                                 where c.Tag == "Simulation"
                                 select c;
      return clearBindingControls;
    }

    /// <summary>
    /// Removes all bindings from simulation-related controls
    /// </summary>
    private void ClearSimulationUIBindings()
    {
      IEnumerable<Control> simulationControls = GetBoundSimulationControls();
      foreach (Control control in simulationControls)
        control.DataBindings.Clear();

      ClearSimulationUI();
    }

    /// <summary>
    /// Removes all values from simulation-related controls
    /// </summary>
    private void ClearSimulationUI()
    {
      lblHydraulicsFile.Text = string.Empty;
      txtHydrologyFile.Text = string.Empty;
      chkUseBaseflow.Checked = false;
      txtTitle1.Text = string.Empty;
      txtTitle2.Text = string.Empty;
    }

    /// <summary>
    /// Enables or disables simulation-related controls
    /// </summary>
    /// <param name="enable">True to enable the controls</param>
    private void EnableSimulationUI(bool enable)
    {
      IEnumerable<Control> simulationControls = GetSimulationControls();
      foreach (Control control in simulationControls)
        control.Enabled = enable;
    }

    /// <summary>
    /// Binds simulation-related controls to the Simulation object's properties
    /// </summary>
    /// <param name="simulation">The Simulation of interest</param>
    private void BindSimulationUI(Simulation simulation)
    {
      ClearSimulationUIBindings();
      lblHydraulicsFile.Text =
        System.IO.Path.Combine(_modelBuilderExtension.Model.GetPathIfSet(ModelPathComponent.RootFolder),
        "XPExtran.XPX");
      txtHydrologyFile.DataBindings.Add(new Binding("Text", simulation, "HydrologyFile"));
      chkUseBaseflow.DataBindings.Add(new Binding("Checked", simulation, "UseBaseflow"));
      txtTitle1.DataBindings.Add(new Binding("Text", simulation, "Title1"));
      txtTitle2.DataBindings.Add(new Binding("Text", simulation, "Title2"));
    }

    private void txtHydrologyFile_EditorButtonClick(object sender, Infragistics.Win.UltraWinEditors.EditorButtonEventArgs e)
    {
      dlgOpenFile.Title = "Select hydrology file name";
      if (dlgOpenFile.ShowDialog() == DialogResult.OK)
      {
        txtHydrologyFile.Text = dlgOpenFile.FileName;
      }
    }

    private void btnBuildOptions_Click(object sender, EventArgs e)
    {
      tabModelBuilder.SelectedTab = tabModelBuilder.Tabs["Build Options"];

    }

    private void lstViewStorms_ItemCheckStateChanged(object sender, Infragistics.Win.UltraWinListView.ItemCheckStateChangedEventArgs e)
    {
      int selectedStormId = Convert.ToInt32(e.Item.Key);
      bool isChecked = e.Item.CheckState == CheckState.Checked;

      if (isChecked)
      {
        if (!ModelHasStorm(selectedStormId))
        {
          Storm selectedStorm = GetStormById(selectedStormId);
          string simulationPath = System.IO.Path.Combine(
                        _modelBuilderExtension.Model.GetPathIfSet(ModelPathComponent.RootFolder),
                        selectedStorm.Abbreviation) + System.IO.Path.DirectorySeparatorChar;
          _modelBuilderExtension.Model.Simulations.Add(
            new Simulation()
            {
              Id = selectedStormId,
              Name = GetStormName(selectedStorm),
              HydrologyFile = simulationPath + "runoff.rdt",
              UseBaseflow = true,
              StormAbbreviation = selectedStorm.Abbreviation,
              Title1 = String.Format("Model: {0}", GetStormName(selectedStorm)),
              Title2 = String.Format("TimeFrame: {0}, Created: {1:MM/dd/yyyy HH:mm}",
                _modelBuilderExtension.Model.TimeFrame, DateTime.Now)
            });
        }
        BindSimulationUI(ModelSimulation(selectedStormId));
        UpdateSimulationList();
      }
      else
      {
        // TODO: Prompt to delete first!
        Simulation simulationToDelete =
          _modelBuilderExtension.Model.Simulations.Find(s => s.Id == selectedStormId);
        _modelBuilderExtension.Model.Simulations.Remove(simulationToDelete);
      }
      EnableSimulationUI(isChecked);
    }

    private void lstViewStorms_ItemActivated(object sender, Infragistics.Win.UltraWinListView.ItemActivatedEventArgs e)
    {
      if (e.Item == null)
      {
        ClearSimulationUIBindings();
        EnableSimulationUI(false);
        return;
      }

      int selectedStormId = Convert.ToInt32(e.Item.Key);
      bool isChecked = e.Item.CheckState == CheckState.Checked;

      if (isChecked)
      {
        BindSimulationUI(ModelSimulation(selectedStormId));
        UpdateSimulationList();
      }
      else
      {
        ClearSimulationUIBindings();
      }
      EnableSimulationUI(isChecked);
    }

  }
}
